package com.xiaouyudeguang.common.dicode.resource;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.xiaouyudeguang.common.constants.Headers;
import com.xiaouyudeguang.common.dicode.service.AbstractService;
import com.xiaouyudeguang.common.dicode.service.DiCodeService;
import com.xiaouyudeguang.common.ditable.service.DiTableService;
import com.xiaouyudeguang.common.exception.BusinessException;
import com.xiaouyudeguang.common.respose.ResultDto;
import com.xiaouyudeguang.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

@RestController
public class DiCodeResource {

    @Autowired
    private DiCodeService baseService;

    @Autowired
    private DiTableService ditableService;

    @Autowired
    private ApplicationContext context;

    @Autowired
    protected HttpServletResponse response;

    private AbstractService<?> getHandlerService(String tableName) {
        if (context.containsBean(tableName + "Service")) {
            return ((AbstractService<?>) context.getBean(tableName + "Service"));
        }
        return baseService;
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name}/save")
    public ResultDto<String> save(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                  @RequestHeader(Headers.APP_ID) String appId,
                                  @PathVariable("table_name") String tableName,
                                  @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).save(tenantId, appId, tableName, request));
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name}/saveBatch")
    public ResultDto<Boolean> saveBatch(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                        @RequestHeader(Headers.APP_ID) String appId,
                                        @PathVariable("table_name") String tableName,
                                        @RequestBody ArrayNode dataList) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).saveBatch(tenantId, appId, tableName, dataList));
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "/{table_name}/deleteById/{id}")
    public ResultDto<Boolean> deleteById(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                         @RequestHeader(Headers.APP_ID) String appId,
                                         @PathVariable("table_name") String tableName,
                                         @PathVariable("id") String id) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).deleteById(tenantId, appId, tableName, id));
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "/{table_name}/deleteByIds")
    public ResultDto<Boolean> deleteByIds(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                          @RequestHeader(Headers.APP_ID) String appId,
                                          @PathVariable("table_name") String tableName,
                                          @RequestBody ArrayNode body) throws Exception {
        if (body.isEmpty()) {
            return ResultDto.ok();
        }
        Set<String> ids = new HashSet<>();
        body.forEach(it -> {
            ids.add(it.asText());
        });
        return ResultDto.ok(getHandlerService(tableName).deleteByIds(tenantId, appId, tableName, ids));
    }

    @RequestMapping(method = RequestMethod.PUT, value = "/{table_name}/updateById")
    public ResultDto<Boolean> updateById(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                         @RequestHeader(Headers.APP_ID) String appId,
                                         @PathVariable("table_name") String tableName,
                                         @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).updateById(tenantId, appId, tableName, request));
    }

    @RequestMapping(method = RequestMethod.PUT, value = "/{table_name}/updateByIds")
    public ResultDto<Boolean> updateByIds(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                          @RequestHeader(Headers.APP_ID) String appId,
                                          @PathVariable("table_name") String tableName,
                                          @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).updateByIds(tenantId, appId, tableName, request));
    }

    @RequestMapping(method = RequestMethod.PUT, value = "/{table_name}/updateBatch")
    public ResultDto<Boolean> updateBatch(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                          @RequestHeader(Headers.APP_ID) String appId,
                                          @PathVariable("table_name") String tableName,
                                          @RequestBody ArrayNode dataList) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).updateBatch(tenantId, appId, tableName, dataList));
    }

    @RequestMapping(method = RequestMethod.GET, value = "/{table_name}/getById/{id}")
    public ResultDto<JsonNode> getById(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                       @RequestHeader(Headers.APP_ID) String appId,
                                       @PathVariable("table_name") String tableName,
                                       @PathVariable("id") String id) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).getById(tenantId, appId, tableName, id));
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name}/getByIds")
    public ResultDto<List<ObjectNode>> getByIds(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                                @RequestHeader(Headers.APP_ID) String appId,
                                                @PathVariable("table_name") String tableName,
                                                @RequestBody ArrayNode body) throws Exception {
        if (body.isEmpty()) {
            return ResultDto.ok();
        }
        Set<String> ids = new HashSet<>();
        body.forEach(it -> {
            ids.add(it.asText());
        });
        return ResultDto.ok(getHandlerService(tableName).getByIds(tenantId, appId, tableName, ids));
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name}/tree")
    public ResultDto<List<ObjectNode>> tree(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                            @RequestHeader(Headers.APP_ID) String appId,
                                            @PathVariable("table_name") String tableName,
                                            @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).tree(tenantId, appId, tableName, request));
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name}/export")
    public void export(@RequestHeader(Headers.TENANT_ID) String tenantId,
                       @RequestHeader(Headers.APP_ID) String appId,
                       @PathVariable("table_name") String tableName,
                       @RequestBody ObjectNode request) throws Exception {
        ArrayNode includeKeys = (ArrayNode) request.get("includeKeys");
        Map<String, String> mapping = ditableService.getFieldMapping(tenantId, appId, tableName);
        List<List<String>> headers = new ArrayList<>();
        if (includeKeys.isEmpty()) {
            mapping.forEach((key, value) -> {
                headers.add(Collections.singletonList(value));
            });
        } else {
            includeKeys.forEach(header -> {
                headers.add(Collections.singletonList(mapping.getOrDefault(header.asText(), header.asText())));
            });
        }
        List<ObjectNode> recordList = getHandlerService(tableName).export(tenantId, appId, tableName, request);
        if (recordList.isEmpty()) {
            throw new BusinessException("没有需要导出的数据");
        }
        List<List<Object>> dataList = new ArrayList<>();
        recordList.forEach(it -> {
            List<Object> data = new ArrayList<>();
            includeKeys.forEach(header -> {
                data.add(it.path(mapping.getOrDefault(header.asText(), header.asText())).asText());
            });
            dataList.add(data);
        });
        if (request.has("path")) {
            OutputStream outputStream = Files.newOutputStream(Paths.get(request.get("path").asText()));
            EasyExcel.write(outputStream).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).head(headers).sheet(tableName).doWrite(dataList);
        } else {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + tableName + ".xlsx");
            ExcelWriterSheetBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream()).sheet(tableName);
            excelWriterBuilder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
            excelWriterBuilder.head(headers);
            excelWriterBuilder.doWrite(dataList);
        }
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name}/upload")
    public ResultDto<String> upload(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                    @RequestHeader(Headers.APP_ID) String appId,
                                    @PathVariable("table_name") String tableName,
                                    MultipartFile file) throws Exception {
        if (file.getSize() > 26214400) {
            throw new BusinessException("上传文件不能大于25MB");
        }
        List<Map<String, Object>> dataList = new ArrayList<>();
        EasyExcel.read(file.getInputStream(), new ReadListener<Map<Integer, Object>>() {
            private final Map<Integer, String> headMap = new HashMap<>();

            @Override
            public void invoke(Map<Integer, Object> data, AnalysisContext context) {
                Map<String, Object> node = new HashMap<>();
                data.forEach((index, value) -> {
                    node.put(headMap.get(index), value);
                });
                dataList.add(node);
            }

            @Override
            public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
                headMap.forEach((index, cellData) -> {
                    this.headMap.put(index, cellData.getStringValue());
                });
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {

            }
        }).sheet().doRead();
        String errorFileUrl = getHandlerService(tableName).upload(tenantId, appId, tableName, dataList);
        return StringUtils.isBlank(errorFileUrl) ? ResultDto.ok() : ResultDto.failure(errorFileUrl);
    }

    @RequestMapping(method = RequestMethod.PUT, value = "/{table_name:^(?:(?!oauth).)*$}/{table_api}")
    public ResultDto<Boolean> update(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                     @RequestHeader(Headers.APP_ID) String appId,
                                     @PathVariable("table_name") String tableName,
                                     @PathVariable("table_api") String tableApi,
                                     @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).update(tenantId, appId, tableName, tableApi, request));
    }

    @RequestMapping(method = RequestMethod.DELETE, value = "/{table_name:^(?:(?!oauth).)*$}/{table_api}")
    public ResultDto<Boolean> delete(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                     @RequestHeader(Headers.APP_ID) String appId,
                                     @PathVariable("table_name") String tableName,
                                     @PathVariable("table_api") String tableApi,
                                     @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).delete(tenantId, appId, tableName, tableApi, request));
    }

    @RequestMapping(method = RequestMethod.POST, value = "/{table_name:^(?:(?!oauth).)*$}/{table_api}")
    public Object page(@RequestHeader(Headers.TENANT_ID) String tenantId,
                                                @RequestHeader(Headers.APP_ID) String appId,
                                                @PathVariable("table_name") String tableName,
                                                @PathVariable("table_api") String tableApi,
                                                @RequestBody JsonNode request) throws Exception {
        return ResultDto.ok(getHandlerService(tableName).page(tenantId, appId, tableName, tableApi, request));
    }
}
